/*
 * @(#)SatrunPropertySourceLocator.java 1.0 2019年10月29日上午11:24:33
 *
 * Saturn Framework Co.Limt
 * Copyright (c) 2012-2019 tanks, Inc. All rights reserved.
 */
package com.saturn.cloud.config.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;

import com.alibaba.fastjson.JSONObject;
import com.saturn.cloud.config.SatrunConfigProperties;
import com.saturn.cloud.config.util.ConfigInfo;
import com.saturn.cloud.config.util.HomeFolderUtils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;

/**
 * <dl>
 *    <dt><b>Title:</b></dt>
 *    <dd>
 *    	none
 *    </dd>
 *    <dt><b>Description:</b></dt>
 *    <dd>
 *    	<p>none
 *    </dd>
 * </dl>
 *
 * @author tanks
 * @version 1.0, 2019年10月29日
 * @since saturn-config-spring-boot-starter
 * 
 */
@Order(0)
public class SatrunPropertySourceLocator implements PropertySourceLocator {

	private static final Logger log = LoggerFactory.getLogger(SatrunPropertySourceLocator.class);
	private static final String SATURN_PROPERTY_SOURCE_NAME = "SATURN";
	private static final String LOCAL_CACHE_CONFIG_FILE = "-config.properties";
	private static final String APPLICATION_NAME_KEY = "spring.application.name";
	private static final String APPLICATION_PORT_KEY = "server.port";
	
	private SatrunConfigProperties satrunConfigProperties;
	
	public SatrunPropertySourceLocator(SatrunConfigProperties satrunConfigProperties) {
		this.satrunConfigProperties = satrunConfigProperties;
	}
	
	@Override
	public PropertySource<?> locate(Environment environment) {
		if (null == satrunConfigProperties || satrunConfigProperties.getServerUrls()==null ) {
			log.warn("no instance of config service found, can't load config from saturn");
			return null;
		}
		String data = StrUtil.EMPTY;
		for(String url:satrunConfigProperties.getServerUrls()) {
			log.info("get config data from saturn config center server url:" + url);
			data = HttpUtil.get(satrunConfigProperties.getServerUrls().get(0));
			if( StrUtil.isBlank(data) ) {
				log.error("can't get any data from saturn config center server");
			}else {
				break;
			}
		}
		if( StrUtil.isBlank(data) ) {
			log.error("can't get config data from saturn config server,please check the cluster servers is alive");
			if( this.satrunConfigProperties.isEnableCache() ) {
				HomeFolderUtils.createHomeFolderIfNotExisted();
				String port = environment.getProperty(APPLICATION_PORT_KEY,StrUtil.EMPTY);
				String applicationName = environment.getProperty(APPLICATION_NAME_KEY,StrUtil.EMPTY) + port;
				String localConfigProp = HomeFolderUtils.getFilePathInHomeFolder(applicationName + LOCAL_CACHE_CONFIG_FILE);
				File file = new File(localConfigProp);
				if( file.exists() ) {
					Properties properties = new Properties();
					InputStream ins = null;
					try {
						ins = new FileInputStream(file);
						properties.load(ins);
						CompositePropertySource composite = new CompositePropertySource(SATURN_PROPERTY_SOURCE_NAME);
						SaturnPropertySource ps = new SaturnPropertySource(SATURN_PROPERTY_SOURCE_NAME,propertiesToMap(properties));
						composite.addFirstPropertySource(ps);
						return composite;
					}catch(Exception ex) {
						log.info("can't load local config properties from file:" + localConfigProp);
					}finally {
						if( ins!=null ) {
							try {
								ins.close();
							} catch (IOException ex) {
								//ignore
							} 
						}
					}
				}
			}
			return null;
		}else {
			FileOutputStream ops = null;
			OutputStreamWriter writer = null;
			try {
				List<ConfigInfo> configInfos = JSONObject.parseArray(data, ConfigInfo.class);
				if( configInfos==null || configInfos.size()==0 ){
					log.info("Can't convert config server's data to ConfigConfig objects");
					return null;
				}
				Properties properties = new Properties();
				String port = StrUtil.EMPTY;
				for(ConfigInfo configInfo:configInfos){
					if( APPLICATION_PORT_KEY.equals(configInfo.getConfigKey()) ) {
						port = configInfo.getConfigValue();
					}
					properties.put(configInfo.getConfigKey(), configInfo.getConfigValue());
				}
				CompositePropertySource composite = new CompositePropertySource(SATURN_PROPERTY_SOURCE_NAME);
				SaturnPropertySource ps = new SaturnPropertySource(SATURN_PROPERTY_SOURCE_NAME,propertiesToMap(properties));
				composite.addFirstPropertySource(ps);
				if( this.satrunConfigProperties.isEnableCache() ) {
					HomeFolderUtils.createHomeFolderIfNotExisted();
					if( StrUtil.isBlank(port) ) {
						port = environment.getProperty(APPLICATION_PORT_KEY,StrUtil.EMPTY);
					}
					String applicationName = environment.getProperty(APPLICATION_NAME_KEY,StrUtil.EMPTY) + port;
					String localConfigProp = HomeFolderUtils.getFilePathInHomeFolder(applicationName + LOCAL_CACHE_CONFIG_FILE);
					log.info("write config center properties to loacal file:" + localConfigProp);
					ops = new FileOutputStream(localConfigProp);
					writer = new OutputStreamWriter(ops,satrunConfigProperties.getEncode());
					properties.store(writer, StrUtil.EMPTY);
				}
				return composite;
			}catch(Exception ex) {
				log.error("Load config data error",ex);
				return null;
			}finally {
				if( ops!=null ) {
					try {
						ops.close();
					} catch (IOException ex) {
						//ignore error
					}
				}
				if( writer!=null ) {
					try {
						writer.close();
					} catch (IOException e) {
						//ignore error
					}
				}
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, Object> propertiesToMap(Properties properties) {
		Map<String, Object> result = new HashMap<>(16);
		Enumeration<String> keys = (Enumeration<String>) properties.propertyNames();
		while (keys.hasMoreElements()) {
			String key = keys.nextElement();
			Object value = properties.getProperty(key);
			if (value != null) {
				result.put(key, ((String) value).trim());
			}
			else {
				result.put(key, null);
			}
		}
		return result;
	}

}
